#include "crane_lcd_mcu.h"

#if USE_CRANE_LCD_MCU
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../common/utils.h"
#include "../../board.h"
#include "crane_lcdc.h"

#define SMPN_CTRL    0x188
#define SLV_PORT     0x18C
#define IRQ_ISR_RAW  0x1C4
#define MISC_CTRL    0x1F8
#define PN_CTRL0     0x190
#define ISA_RXDATA   0x144

#define IRQ_MCU_DONE BIT_19

#if USE_LCD_MCU_DCX_GPIO
/*lcd_mfp_cfgs also configs GPIO_81 as DCX pin, gpio id can be updated by customer */
#define MCU_DCX_GPIO  81 //LCD_GPIO_81
#endif

typedef struct {
    uint32_t sclk; /*KHz*/
    uint16_t cur_path;
    uint16_t status;
    uint16_t reserved;
    const struct mcu_info *info;
} mcu_context;

static mcu_context g_mcu_ctx;

static void mcu_enable(void)
{
    lcdc_set_bits(SMPN_CTRL, BIT_0);
}

static void mcu_disable(void)
{
    lcdc_write(SMPN_CTRL, 0);
}

__attribute__ ((unused))static void lcd_mcu_set_cs(uint32_t enable)
{
    if(enable)
        lcdc_set_bits(SMPN_CTRL, BIT_1);
    else
        lcdc_clear_bits(SMPN_CTRL, BIT_1);
}

static void set_mcu_path(uint32_t path)
{
    if(path == MCU_PATH_IMAGE)
        lcdc_clear_bits(SMPN_CTRL, BIT_4);
    else
        lcdc_set_bits(SMPN_CTRL, BIT_4);

    g_mcu_ctx.cur_path = path;
}

static void trigger_mcu(uint32_t value)
{
    int reg = 0;
    lcd_mcu_set_cs(1);
    lcdc_write(SLV_PORT, value);
    reg = lcdc_read(IRQ_ISR_RAW);
    while (0 == (reg & IRQ_MCU_DONE)){
        reg = lcdc_read(IRQ_ISR_RAW);
    }
    lcdc_write(IRQ_ISR_RAW, ~IRQ_MCU_DONE);
    lcd_mcu_set_cs(0);
}

static void mcu_config(const struct mcu_info *info, uint32_t sclk, uint8_t path)
{
    uint32_t reg = 0,scycle,rl,rh,wl,wh;

    scycle = 1000000/sclk; /*ns*/
    rl = (info->timing->rlpw + scycle - 1) / scycle - 1;
    rh = (info->timing->rhpw + scycle - 1) / scycle - 1;
    wl = (info->timing->wlpw + scycle - 1) / scycle - 1;
    wh = (info->timing->whpw + scycle - 1) / scycle - 1;

    reg = rl<<28 | rh<<24 | wl<<20 | wh<<16;

    if(info->format == MCU_FORMAT_RGB565){
        reg |= (2 << 8);
    } else {
        reg |= (1 << 8);
    }

    if(info->endian == MCU_ENDIAN_MSB)
        reg |= BIT_6;

    if(info->device_id == 0)
        reg &= ~BIT_5;

    if(path == MCU_PATH_IMAGE)
        reg &= ~BIT_4;
    else
        reg |= BIT_4;

    g_mcu_ctx.cur_path = path;

    if(info->bus_mode == MCU_BUS_8080)
        reg &= ~BIT_2;

    reg |= BIT_0; /*enable mcu interface*/
    lcdc_write(SMPN_CTRL, reg);

    lcdc_set_bits(MISC_CTRL, BIT_1); /*should be set, otherwith color will error*/

    lcdc_set_bits(PN_CTRL0, BIT_25); /*delay cs rising edge one cycle*/
}

int lcd_mcu_init(uint32_t sclk, struct mcu_info *info)
{
    uint32_t scycle,rl,rh,wl,wh;

    //printf("mcu_init (dev_id:%d)+++\r\n", info->device_id);

    if(info == NULL || info->timing == NULL){
        printf("ERROR: lcd_mcu_init, Invalid param\n");
        return -1;
    }

    scycle = 1000000/sclk; /*ns*/
    rl = (info->timing->rlpw + scycle - 1) / scycle - 1;
    rh = (info->timing->rhpw + scycle - 1) / scycle - 1;
    wl = (info->timing->wlpw + scycle - 1) / scycle - 1;
    wh = (info->timing->whpw + scycle - 1) / scycle - 1;
    if((rl > 15) || (rh > 15) || (wl > 15) || (wh > 15)){
        printf("ERROR: mcu_init, can't get mcu's clk!\n");
        return -1;
    }

    mcu_enable();
    set_mcu_path(MCU_PATH_IMAGE);
#if USE_LCD_MCU_DCX_GPIO
    gpio_direction_output(MCU_DCX_GPIO);
#endif
    g_mcu_ctx.sclk = sclk;
    g_mcu_ctx.info = info;
    g_mcu_ctx.status = MCU_STATUS_INIT;

    return 0;
}

int lcd_mcu_write_cmd(uint32_t cmd, uint32_t bits)
{
    int reg;

    mcu_config(g_mcu_ctx.info, g_mcu_ctx.sclk, MCU_PATH_REGISTER);
#if USE_LCD_MCU_DCX_GPIO
    gpio_set_value(MCU_DCX_GPIO, 0);
    lv_udelay(1);
#endif
    switch(bits){
    case 8:
        reg = (cmd & 0xFF) | BIT_8;
        trigger_mcu(reg);
        break;
    case 16:
        reg = (cmd & 0xFF) | BIT_8 | ((cmd & 0xFF00)<<8) | BIT_24;
        trigger_mcu(reg);
        break;
    case 24:
        reg = (cmd & 0xFF) | BIT_8 | ((cmd & 0xFF00)<<8) | BIT_24;
        trigger_mcu(reg);
        reg = ((cmd & 0xFF0000)>>16) | BIT_8;
        trigger_mcu(reg);
        break;
    case 32:
        reg = (cmd & 0xFF) | BIT_8 | ((cmd & 0xFF00)<<8) | BIT_24;
        trigger_mcu(reg);
        reg = ((cmd & 0xFF0000)>>16) | BIT_8 | ((cmd & 0xFF000000)>>8) | BIT_24;
        trigger_mcu(reg);
        break;
    default:
        printf("ERROR: lcd_mcu_write_cmd, Invalid bits!\r\n");
        return -1;
    }
    return 0;
}

int lcd_mcu_write_data(uint32_t data, uint32_t bits)
{
    int reg;

    mcu_config(g_mcu_ctx.info, g_mcu_ctx.sclk, MCU_PATH_REGISTER);
#if USE_LCD_MCU_DCX_GPIO
    gpio_set_value(MCU_DCX_GPIO, 1);
    lv_udelay(1);
#endif

    switch(bits){
    case 8:
        reg = (data & 0xFF) | BIT_8 | BIT_15;
        trigger_mcu(reg);
        break;
    case 16:
        reg = (data & 0xFF) | BIT_8 | BIT_15 | ((data & 0xFF00)<<8) | BIT_24
          | BIT_31;
        trigger_mcu(reg);
        break;
    case 24:
        reg = (data & 0xFF) | BIT_8 | BIT_15 | ((data & 0xFF00)<<8) | BIT_24
          | BIT_31;
        trigger_mcu(reg);
        reg = ((data & 0xFF0000)>>16) | BIT_8;
        trigger_mcu(reg);
        break;
    case 32:
        reg = (data & 0xFF) | BIT_8 | BIT_15 | ((data & 0xFF00)<<8) | BIT_24 | BIT_31;
        trigger_mcu(reg);
        reg = ((data & 0xFF0000)>>16) | BIT_8 | BIT_15 | ((data & 0xFF000000)>>8) | BIT_24 | BIT_31;
        trigger_mcu(reg);
        break;
    default:
        printf("ERROR: lcd_mcu_write_data, Invalid bits!\r\n");
        return -1;
    }
    return 0;
}


int lcd_mcu_read_data(uint32_t cmd, uint32_t cmd_bits, uint32_t *data, uint32_t data_bits)
{
    int reg;
    int readdata;
    mcu_config(g_mcu_ctx.info, g_mcu_ctx.sclk, MCU_PATH_REGISTER);
#if USE_LCD_MCU_DCX_GPIO
    gpio_set_value(MCU_DCX_GPIO, 0);
    lv_udelay(1);
#endif

    switch(cmd_bits){
    case 8:
    case 24:
        if(cmd_bits == 8){
            reg = (cmd & 0xFF) | BIT_8;
#if USE_LCD_MCU_DCX_GPIO
            trigger_mcu(reg);
#endif
        } else {
            reg = (cmd & 0xFF) | BIT_8 | ((cmd & 0xFF00)<<8) | BIT_24;
            trigger_mcu(reg);
            reg = ((cmd & 0xFF0000)>>16) | BIT_8;
#if USE_LCD_MCU_DCX_GPIO
            trigger_mcu(reg);
#endif
        }
#if USE_LCD_MCU_DCX_GPIO
    gpio_set_value(MCU_DCX_GPIO, 1);
    lv_udelay(1);
#endif
        switch(data_bits){
        case 8:
#if USE_LCD_MCU_DCX_GPIO
            reg = BIT_9 | BIT_15;
            trigger_mcu(reg);//The first read is dummy
            reg = BIT_9 | BIT_15;
            trigger_mcu(reg);
            *data = lcdc_read(ISA_RXDATA) & 0xFF;
#else
            reg |= BIT_25 | BIT_31;
            trigger_mcu(reg);//The first read is dummy
            reg = BIT_9 | BIT_15;
            trigger_mcu(reg);
            *data = lcdc_read(ISA_RXDATA) & 0xFF;
#endif
            break;
        case 16:
#if USE_LCD_MCU_DCX_GPIO
            reg = BIT_9 | BIT_15;
            trigger_mcu(reg);//The first read is dummy
            reg = BIT_9 | BIT_15;
            trigger_mcu(reg);
            readdata = lcdc_read(ISA_RXDATA) & 0xFF;
            reg = BIT_9 | BIT_15;
            trigger_mcu(reg);
            *data = (readdata << 8) | (lcdc_read(ISA_RXDATA) & 0xFF);
#else
            reg |= BIT_25 | BIT_31;
            trigger_mcu(reg);//The first read is dummy
            reg = BIT_9 | BIT_15 | BIT_25 | BIT_31;
            trigger_mcu(reg);
            *data = lcdc_read(ISA_RXDATA) & 0xFFFF;
#endif
            break;
        case 24:
#if USE_LCD_MCU_DCX_GPIO
            reg = BIT_9 | BIT_15;
            trigger_mcu(reg);//The first read is dummy
            reg = BIT_9 | BIT_15;
            trigger_mcu(reg);
            readdata = lcdc_read(ISA_RXDATA) & 0xFF;
            reg = BIT_9 | BIT_15;
            trigger_mcu(reg);
            readdata = (readdata << 8) | (lcdc_read(ISA_RXDATA) & 0xFF);
            reg = BIT_9 | BIT_15;
            trigger_mcu(reg);
            *data = (readdata << 8) | (lcdc_read(ISA_RXDATA) & 0xFF);
#else
            reg |= BIT_25 | BIT_31;
            trigger_mcu(reg);//The first read is dummy
            reg = BIT_9 | BIT_15 | BIT_25 | BIT_31;
            trigger_mcu(reg);
            readdata = lcdc_read(ISA_RXDATA) & 0xFFFF;
            reg = BIT_9 | BIT_15;
            trigger_mcu(reg);
            *data = (readdata << 8) | (lcdc_read(ISA_RXDATA) & 0xFF);
#endif
            break;
        case 32:
#if USE_LCD_MCU_DCX_GPIO
            reg = BIT_9 | BIT_15;
            trigger_mcu(reg);//The first read is dummy
            reg = BIT_9 | BIT_15;
            trigger_mcu(reg);
            readdata = lcdc_read(ISA_RXDATA) & 0xFF;
            reg = BIT_9 | BIT_15;
            trigger_mcu(reg);
            readdata = (readdata << 8) | (lcdc_read(ISA_RXDATA) & 0xFF);
            reg = BIT_9 | BIT_15;
            trigger_mcu(reg);
            readdata = (readdata << 8) | (lcdc_read(ISA_RXDATA) & 0xFF);
            reg = BIT_9 | BIT_15;
            trigger_mcu(reg);
            *data = (readdata << 8) | (lcdc_read(ISA_RXDATA) & 0xFF);
#else
            reg |= BIT_25 | BIT_31;
            trigger_mcu(reg);//The first read is dummy
            reg = BIT_9 | BIT_15 | BIT_25 | BIT_31;
            trigger_mcu(reg);
            readdata = lcdc_read(ISA_RXDATA) & 0xFFFF;
            reg = BIT_9 | BIT_15 | BIT_25 | BIT_31;
            trigger_mcu(reg);
            *data = (readdata << 16) | (lcdc_read(ISA_RXDATA) & 0xFFFF);
#endif
            break;
        default:
            printf("ERROR: mcu_read_data, Invalid bits!\r\n");
            return -1;
        }
        break;
    case 16:
    case 32:
      if(cmd_bits == 16){
          reg = (cmd & 0xFF) | BIT_8 | ((cmd & 0xFF00)<<8) | BIT_24;
          trigger_mcu(reg);
      } else {
          reg = (cmd & 0xFF) | BIT_8 | ((cmd & 0xFF00)<<8) | BIT_24;
          trigger_mcu(reg);
          reg = ((cmd & 0xFF0000)>>16) | BIT_8 | ((cmd & 0xFF000000)>>8) | BIT_24;
          trigger_mcu(reg);
      }
#if USE_LCD_MCU_DCX_GPIO
    gpio_set_value(MCU_DCX_GPIO, 1);
    lv_udelay(1);
#endif
      switch(data_bits){
      case 8:
          reg = BIT_9 | BIT_15 | BIT_25 | BIT_31;
          trigger_mcu(reg);//The first read is dummy
          *data = (lcdc_read(ISA_RXDATA) & 0xFFFF) >> 8;
          break;
      case 16:
          reg = BIT_9 | BIT_15 | BIT_25 | BIT_31;
          trigger_mcu(reg);//The first read is dummy
          readdata = (lcdc_read(ISA_RXDATA) & 0xFFFF) >> 8;
          reg = BIT_9 | BIT_15;
          trigger_mcu(reg);
          *data = (readdata << 8)|(lcdc_read(ISA_RXDATA) & 0xFF);
          break;
      case 24:
          reg = BIT_9 | BIT_15 | BIT_25 | BIT_31;
          trigger_mcu(reg);//The first read is dummy
          readdata = (lcdc_read(ISA_RXDATA) & 0xFFFF) >> 8;
          reg = BIT_9 | BIT_15 | BIT_25 | BIT_31;
          trigger_mcu(reg);
          *data = (readdata << 16)|(lcdc_read(ISA_RXDATA) & 0xFFFF);
          break;
      case 32:
          reg = BIT_9 | BIT_15 | BIT_25 | BIT_31;
          trigger_mcu(reg);//The first read is dummy
          readdata = (lcdc_read(ISA_RXDATA) & 0xFFFF) >> 8;
          reg = BIT_9 | BIT_15 | BIT_25 | BIT_31;
          trigger_mcu(reg);
          readdata = (readdata << 16)|(lcdc_read(ISA_RXDATA) & 0xFFFF);
          reg = BIT_9 | BIT_15;
          trigger_mcu(reg);
          *data = (readdata << 8)|(lcdc_read(ISA_RXDATA) & 0xFF);
          break;
      default:
          printf("ERROR: mcu_read_data, Invalid bits!\r\n");
          return -1;
      }
      break;
      default:
          printf("ERROR: mcu_read_data, Invalid bits!\r\n");
          return -1;
    }
    return 0;
}

int lcd_mcu_write_cmd_data(uint32_t cmd, uint32_t cmd_bits, uint32_t data, uint32_t data_bits)
{
    int reg;
    mcu_config(g_mcu_ctx.info, g_mcu_ctx.sclk, MCU_PATH_REGISTER);
#if USE_LCD_MCU_DCX_GPIO
    gpio_set_value(MCU_DCX_GPIO, 0);
    lv_udelay(1);
#endif

    switch(cmd_bits){
    case 8:
    case 24:
        if(cmd_bits == 8){
            reg = (cmd & 0xFF) | BIT_8;
#if USE_LCD_MCU_DCX_GPIO
            trigger_mcu(reg);
#endif
        } else {
            reg = (cmd & 0xFF) | BIT_8 | ((cmd & 0xFF00)<<8) | BIT_24;
            trigger_mcu(reg);
            reg = ((cmd & 0xFF0000)>>16) | BIT_8;
#if USE_LCD_MCU_DCX_GPIO
            trigger_mcu(reg);
#endif
        }
#if USE_LCD_MCU_DCX_GPIO
        gpio_set_value(MCU_DCX_GPIO, 1);
        lv_udelay(1);
#endif
        switch(data_bits){
        case 8:
#if USE_LCD_MCU_DCX_GPIO
            reg = (data & 0xFF) | BIT_8 | BIT_15;
            trigger_mcu(reg);
#else
            reg |= ((data & 0xFF)<<16) | BIT_24 | BIT_31;
            trigger_mcu(reg);
#endif
            break;
        case 16:
#if USE_LCD_MCU_DCX_GPIO
            reg |= (data & 0xFF) | BIT_8 | BIT_15;
            trigger_mcu(reg);
            reg = ((data & 0xFF00) >> 8) | BIT_8 | BIT_15;
            trigger_mcu(reg);
#else
            reg |= ((data & 0xFF)<<16) | BIT_24 | BIT_31;
            trigger_mcu(reg);
            reg = ((data & 0xFF00) >> 8) | BIT_8 | BIT_15;
            trigger_mcu(reg);
#endif
            break;
        case 24:
#if USE_LCD_MCU_DCX_GPIO
            reg |= (data & 0xFF) | BIT_8 | BIT_15;
            trigger_mcu(reg);
            reg = ((data & 0xFF00) >> 8) | BIT_8 | BIT_15;
            trigger_mcu(reg);
            reg = ((data & 0xFF0000) >> 16) | BIT_8 | BIT_15;
            trigger_mcu(reg);
#else
            reg |= ((data & 0xFF)<<16) | BIT_24 | BIT_31;
            trigger_mcu(reg);
            reg = ((data & 0xFF00) >> 8) | BIT_8 | BIT_15 |
              ((data & 0xFF0000)) | BIT_24 | BIT_31;
            trigger_mcu(reg);
#endif
            break;
        case 32:
#if USE_LCD_MCU_DCX_GPIO
            reg |= (data & 0xFF) | BIT_8 | BIT_15;
            trigger_mcu(reg);
            reg = ((data & 0xFF00) >> 8) | BIT_8 | BIT_15;
            trigger_mcu(reg);
            reg = ((data & 0xFF0000) >> 16) | BIT_8 | BIT_15;
            trigger_mcu(reg);
            reg = ((data & 0xFF000000) >> 24) | BIT_8 | BIT_15;
            trigger_mcu(reg);
#else
            reg |= ((data & 0xFF)<<16) | BIT_24 | BIT_31;
            trigger_mcu(reg);
            reg = ((data & 0xFF00) >> 8) | BIT_8 | BIT_15 |
              ((data & 0xFF0000)) | BIT_24 | BIT_31;
            trigger_mcu(reg);
            reg = ((data & 0xFF000000)>>24) | BIT_8 | BIT_15;
            trigger_mcu(reg);
#endif
            break;
        default:
            printf("ERROR: mcu_write_cmd_data, Invalid bits!\r\n");
            return -1;
        }
        break;
      case 16:
      case 32:
        if(cmd_bits == 16){
            reg = (cmd & 0xFF) | BIT_8 | ((cmd & 0xFF00)<<8) | BIT_24;
            trigger_mcu(reg);
        } else {
            reg = (cmd & 0xFF) | BIT_8 | ((cmd & 0xFF00)<<8) | BIT_24;
            trigger_mcu(reg);
            reg = ((cmd & 0xFF0000)>>16) | BIT_8 | ((cmd & 0xFF000000)>>8) | BIT_24;
            trigger_mcu(reg);
        }
#if USE_LCD_MCU_DCX_GPIO
        gpio_set_value(MCU_DCX_GPIO, 1);
        lv_udelay(1);
#endif
        switch(data_bits){
        case 8:
            reg = (data & 0xFF) | BIT_8 | BIT_15;
            trigger_mcu(reg);
            break;
        case 16:
            reg = (data & 0xFF) | BIT_8 | BIT_15 | ((data & 0xFF00)<<8) | BIT_24
              | BIT_31;
            trigger_mcu(reg);
            break;
        case 24:
            reg = (data & 0xFF) | BIT_8 | BIT_15 | ((data & 0xFF00)<<8) | BIT_24
              | BIT_31;
            trigger_mcu(reg);
            reg = ((data & 0xFF0000)>>16) | BIT_8;
            trigger_mcu(reg);
            break;
        case 32:
            reg = (data & 0xFF) | BIT_8 | BIT_15 | ((data & 0xFF00)<<8) | BIT_24
              | BIT_31;
            trigger_mcu(reg);
            reg = ((data & 0xFF0000)>>16) | BIT_8 | BIT_15 | ((data & 0xFF000000)<<8)
              | BIT_24 | BIT_31;
            trigger_mcu(reg);
            break;
        default:
            printf("ERROR: mcu_write_cmd_data, Invalid bits!\r\n");
            return -1;
        }
        break;
    default:
        printf("ERROR: mcu_write_cmd_data, Invalid bits!\r\n");
        return -1;
    }
    return 0;
}

int lcd_mcu_before_refresh(void)
{
    mcu_config(g_mcu_ctx.info, g_mcu_ctx.sclk, MCU_PATH_IMAGE);
    lcd_mcu_set_cs(1);
#if USE_LCD_MCU_DCX_GPIO
    gpio_set_value(MCU_DCX_GPIO, 1);
    lv_udelay(1);
#endif

    return 0;
}

int lcd_mcu_after_refresh(void)
{
    set_mcu_path(MCU_PATH_REGISTER);
    lcd_mcu_set_cs(0);
    return 0;
}

void lcd_mcu_uninit()
{
    if(g_mcu_ctx.status != MCU_STATUS_INIT){
        printf("ERROR: lcd_mcu_uninit, This mcu device has not been initialized!\r\n");
        return;
    }

    g_mcu_ctx.status = MCU_STATUS_UNINIT;

    mcu_disable();
}

#endif
